﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.IO;
using System.Net.Mime;
using System.Net;
using System.Xml;
using System.Xml.XPath;
using System.Reflection;

namespace HelperService
{
  class TableHelper
  {
    #region Variables, Constructors, Helpers

    string _Account;

    public TableHelper()
    {
      _Account = RESTHelper.GetAccount(string.Empty);
    }

    public TableHelper(string Account)
    {
      _Account = RESTHelper.GetAccount(Account);
    }

    private HttpWebResponse RESTExec(string Method, string Resource)
    {
      return RESTExec(Method, Resource, string.Empty, string.Empty);
    }

    private HttpWebResponse RESTExec(string Method, string Resource, string RequestBody, string IfMatch)
    {
      string uri = @"http://" + _Account + ".table.core.windows.net/" + Resource;

      DateTime now = DateTime.UtcNow;

      // setup the web request
      HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
      request.Method = Method;
      request.ContentType = "application/atom+xml";
      request.Headers.Add("x-ms-date", now.ToString("R", System.Globalization.CultureInfo.InvariantCulture));

      // Add the authorization header
      request.Headers.Add("Authorization", RESTHelper.SharedKey(_Account, GenSignature(_Account, Method, Resource, now)));

      // Add the If-Match header if needed
      if (!string.IsNullOrEmpty(IfMatch))
      {
        request.Headers.Add("If-Match", IfMatch);
      }

      // Set the request body and content length
      if (string.IsNullOrEmpty(RequestBody))
      {
        request.ContentLength = 0;
      }
      else
      {
        byte[] ba = new ASCIIEncoding().GetBytes(RequestBody);

        request.ContentLength = ba.Length;
        request.GetRequestStream().Write(ba, 0, ba.Length);
        request.GetRequestStream().Close();
      }

      return (HttpWebResponse)request.GetResponse();
    }

    private string BuildBody(string TableName, string PartitionKey, string RowKey, object obj)
    {
      // Build the XML to pass in the body of the request
      string fmt = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n";
      fmt += "<entry xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\n";
      fmt += "<updated>{1}Z</updated>\n";
      fmt += "<title /><author><name /></author><id /><content type=\"application/xml\">\n";
      fmt += "<m:properties>\n";

      fmt += "<d:PartitionKey>" + PartitionKey + "</d:PartitionKey>\n";
      fmt += "<d:RowKey>" + RowKey + "</d:RowKey>";

      // Use reflection to retrieve all of the properties from the object
      Type t = obj.GetType();
      PropertyInfo[] pi = t.GetProperties();
      MethodInfo mi;
      foreach (PropertyInfo p in pi)
      {
        mi = p.GetGetMethod();
        fmt += string.Format("<d:{0}>{1}</d:{0}>\n", p.Name, mi.Invoke(obj, null).ToString());
      }

      fmt += "</m:properties></content></entry>";

      return string.Format(fmt, TableName, DateTime.UtcNow.ToString("s"));
    }

    private static string GenSignature(string Account, string Method, string Resource, DateTime now)
    {
      // For a table, you need to use this Canonical form:
      //  VERB + "\n" +
      //  Content - MD5 + "\n" +
      //  Content - Type + "\n" +
      //  Date + "\n" +
      //  CanonicalizedResources;

      // Verb
      string signature = Method + "\n";

      // Content-MD5
      signature += "\n";

      // Content-Type
      signature += "application/atom+xml\n";

      // Date
      signature += now.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + "\n";

      // remove the query string
      int i = Resource.IndexOf("?");
      if (i > 0)
      {
        Resource = Resource.Substring(0, i);
      }

      // Canonicalized Resource
      // Format is /{0}/{1} where 0 is name of the account and 1 is resources URI path
      signature += "/" + Account + "/" + Resource;

      return signature;
    }

    #endregion

    #region Table Methods
    public List<string> ListTables()
    {
      List<string> tables = new List<string>();
      // process the request
      try
      {
        using (HttpWebResponse res = RESTExec("GET", "Tables"))
        {
          // Display the list of tables
          using (XmlReader reader = XmlReader.Create(res.GetResponseStream()))
          {
            //Console.WriteLine("Tables in Account: " + _Account);
            while (reader.ReadToFollowing("d:TableName"))
            {
              tables.Add(reader.ReadElementContentAsString());
            }
          }
        }
      }
      catch (WebException ex)
      {
        RESTHelper.DisplayWebException(ex);
      }
      return tables;
    }

    public bool CreateTable(string TableName)
    {
      bool created = false;
      try
      {
        string fmt = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n";
        fmt += "<entry xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\n";
        fmt += "<title />\n";
        fmt += "<updated>{1}Z</updated>\n";
        fmt += "<author><name /></author>\n";
        fmt += "<id />\n";
        fmt += "<content type=\"application/xml\">\n<m:properties>\n<d:TableName>{0}</d:TableName>\n</m:properties>\n</content>\n</entry>";

        string body = string.Format(fmt, TableName, DateTime.UtcNow.ToString("s"));

        HttpWebResponse res = RESTExec("POST", "Tables", body, string.Empty);
        if (res.StatusCode == HttpStatusCode.Created)
        {
          created = true;
        }
        res.Close();
      }
      catch (WebException ex)
      {
        RESTHelper.DisplayWebException(ex);
      }
      return created;
    }

    public bool DeleteTable(string TableName)
    {
      bool deleted = false;
      try
      {
        HttpWebResponse res = RESTExec("DELETE", string.Format("Tables('{0}')", TableName));
        if (res.StatusCode == HttpStatusCode.NoContent)
        {
          deleted = true;
        }
        res.Close();
      }
      catch (WebException ex)
      {
        RESTHelper.DisplayWebException(ex);
      }
      return deleted;
    }

  //<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>
  //<feed xml:base=\"http://remotify.table.core.windows.net/\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">
  //  <title type=\"text\">WP7DeviceTable</title>
  //  <id>http://remotify.table.core.windows.net/WP7DeviceTable</id>
  //  <updated>2011-03-20T21:55:48Z</updated>
  //  <link rel=\"self\" title=\"WP7DeviceTable\" href=\"WP7DeviceTable\" />
  //  <entry m:etag=\"W/&quot;datetime'2011-03-20T20%3A30%3A01.5256906Z'&quot;\">
  //    <id>http://remotify.table.core.windows.net/WP7DeviceTable(PartitionKey='Devices',RowKey='1001')</id>
  //    <title type=\"text\"></title>
  //    <updated>2011-03-20T21:55:48Z</updated>
  //    <author><name /></author>
  //    <link rel=\"edit\" title=\"WP7DeviceTable\" href=\"WP7DeviceTable(PartitionKey='Devices',RowKey='1001')\" />
  //    <category term=\"remotify.WP7DeviceTable\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />
  //    <content type=\"application/xml\">
  //      <m:properties>
  //        <d:PartitionKey>Devices</d:PartitionKey>
  //        <d:RowKey>1001</d:RowKey>
  //        <d:Timestamp m:type=\"Edm.DateTime\">2011-03-20T20:30:01.5256906Z</d:Timestamp>
  //        <d:DeviceID>1001</d:DeviceID>
  //        <d:DeviceName>LinuxClient</d:DeviceName>
  //      </m:properties>
  //    </content>
  //  </entry>
  //</feed>

    public List<CommTranslate> GetCommands()
    {
      string table = "WP7CommandsTable";
      List<CommTranslate> commands = new List<CommTranslate>();
      try
      {
        string deviceID = string.Empty;
        string tagOfCommand = string.Empty;
        string actualCommand = string.Empty;
        string commandID = string.Empty;
        // execute the query
        using (HttpWebResponse res = RESTExec("GET", table))
        {
          // load the xml
          XmlDocument doc = new XmlDocument();
          doc.Load(res.GetResponseStream());

          XmlNodeList nodes = doc.GetElementsByTagName("m:properties");

          // process each row
          foreach (XmlNode n in nodes)
          {
            // process each column
            foreach (XmlNode n2 in n.ChildNodes)
            {
              if (n2.Name.Substring(2) == "CommandID")
                commandID = n2.InnerText;

              if (n2.Name.Substring(2) == "DeviceID")
                deviceID = n2.InnerText;

              if (n2.Name.Substring(2) == "TagOfCommand")
                tagOfCommand = n2.InnerText;

              if (n2.Name.Substring(2) == "ActualComm")
                actualCommand = n2.InnerText;
            }
            if (!string.IsNullOrEmpty(deviceID) && Int16.Parse(deviceID) > 100 && !string.IsNullOrEmpty(tagOfCommand) && !string.IsNullOrEmpty(actualCommand))
            {
              CommTranslate command = new CommTranslate();
              command.DeviceID = deviceID;
              command.TagOfCommand = tagOfCommand;
              command.ActualComm = actualCommand;
              command.CommandID = commandID;
              commands.Add(command);
            }
            //DeviceInfo.Add(deviceID, deviceName);
          }
        }
      }
      catch (WebException ex)
      {
        RESTHelper.DisplayWebException(ex);
      }
      return commands;
    }


    public List<DeviceInfo> DisplayTable(string Table)
    {
      //Dictionary<string, string> DeviceInfo = new Dictionary<string, string>();
      List<DeviceInfo> devices = new List<DeviceInfo>();
      try
      {
        string deviceID = string.Empty;
        string deviceName = string.Empty;
        // execute the query
        using (HttpWebResponse res = RESTExec("GET", Table))
        {
          // load the xml
          XmlDocument doc = new XmlDocument();
          doc.Load(res.GetResponseStream());

          XmlNodeList nodes = doc.GetElementsByTagName("m:properties");

          // process each row
          foreach (XmlNode n in nodes)
          {
            // process each column
            foreach (XmlNode n2 in n.ChildNodes)
            {
              if (n2.Name.Substring(2) == "DeviceID")
                deviceID = n2.InnerText;

              if (n2.Name.Substring(2) == "DeviceName")
                deviceName = n2.InnerText;
            }
            if (!string.IsNullOrEmpty(deviceID) && Int16.Parse(deviceID) > 100 && !string.IsNullOrEmpty(deviceName))
            {
              DeviceInfo device = new DeviceInfo();
              device.DeviceID = deviceID;
              device.DeviceName = deviceName;
              devices.Add(device);
            }
            //DeviceInfo.Add(deviceID, deviceName);
          }
        }
      }
      catch (WebException ex)
      {
        RESTHelper.DisplayWebException(ex);
      }
      return devices;
    }

    #endregion

    #region Row Methods

    public bool DeviceTable_InsertRow(string TableName, string PartitionKey, string RowKey, DeviceInfo device)
    {
      bool created = false;
      try
      {
        HttpWebResponse res = RESTExec("POST", TableName, BuildBody(TableName, PartitionKey, RowKey, device), string.Empty);
        if (res.StatusCode == HttpStatusCode.Created)
        {
          created = true;
        }
        res.Close();
      }
      catch (WebException ex)
      {
        RESTHelper.DisplayWebException(ex);
      }
      return created;
    }

    public bool CommandsTable_InsertRow(string TableName, string PartitionKey, string RowKey, CommTranslate command)
    {
      bool created = false;
      try
      {
        HttpWebResponse res = RESTExec("POST", TableName, BuildBody(TableName, PartitionKey, RowKey, command), string.Empty);
        if (res.StatusCode == HttpStatusCode.Created)
        {
          created = true;
        }
        res.Close();
      }
      catch (WebException ex)
      {
        RESTHelper.DisplayWebException(ex);
      }
      return created;
    }

    public void UpdateRow(string TableName, string PartitionKey, string RowKey, object obj)
    {
      string cmd = string.Format("{0}(PartitionKey='{1}',RowKey='{2}')", TableName, PartitionKey, RowKey);

      HttpWebResponse res = RESTExec("MERGE", cmd, BuildBody(TableName, PartitionKey, RowKey, obj), "*");
      res.Close();
    }

    public void DeleteRow(string TableName, string PartitionKey, string RowKey)
    {
      string cmd = string.Format("{0}(PartitionKey='{1}',RowKey='{2}')", TableName, PartitionKey, RowKey);

      HttpWebResponse res = RESTExec("DELETE", cmd, string.Empty, "*");
      res.Close();
    }
    #endregion
  }

  internal class RESTHelper
  {
    public static string DisplayWebException(WebException ex)
    {
      Console.WriteLine(ex.Message);

      string s = string.Empty;

      if (ex.Response != null)
      {
        s = new System.IO.StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
        Console.WriteLine(s + "\n");
      }
      Console.WriteLine();

      return s;
    }

    public static string GetAccount()
    {
      return GetAccount(string.Empty);
    }

    public static string GetAccount(string Account)
    {
      return "remotify";
      //// default account
      //if (string.IsNullOrEmpty(Account) || Account.Equals("Account1", StringComparison.OrdinalIgnoreCase))
      //  return "remotify";

      //if (Account.Equals("remotify", StringComparison.OrdinalIgnoreCase))
      //  return "remotify";

      //if (Account.Equals("Account3", StringComparison.OrdinalIgnoreCase))
      //  return "Account3";

      //throw new Exception("Invalid Account: " + Account);
    }

    public static string GetSecret(string Account)
    {
      return "syKvd1TrzV6P6Hw1gtBCREOAhF72qRTaBCO8J3a53BOOSp4ZHXmajKoD+Yov+c7LG6P2SSHYvZjdcCPnotWG+g==";
      //if (Account == "remotify")
      //  return "syKvd1TrzV6P6Hw1gtBCREOAhF72qRTaBCO8J3a53BOOSp4ZHXmajKoD+Yov+c7LG6P2SSHYvZjdcCPnotWG+g==";

      //if (Account == "Account2")
      //  return "Secret2";

      //if (Account == "Account3")
      //  return "Secret3";

      //throw new Exception("Invalid Account: " + Account);
    }

    public static string SharedKey(string Account, string signature)
    {

      // Hash-based Message Authentication Code (HMAC) using SHA256 hash
      System.Security.Cryptography.HMACSHA256 hasher = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(GetSecret(Account)));

      byte[] hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(signature));

      // return the Shared Key
      return "SharedKey " + Account + ":" + System.Convert.ToBase64String(hash);
    }
  }
}
